import CollapsibleEntry from '@bpmn-io/properties-panel/lib/components/entries/Collapsible';
import ListEntry from '@bpmn-io/properties-panel/lib/components/entries/List';
import SelectEntry from '@bpmn-io/properties-panel/lib/components/entries/Select';
import TextField from '@bpmn-io/properties-panel/lib/components/entries/TextField';

import FormFieldConstraint from './FormFieldConstraint';
import FormFieldProperty from './FormFieldProperty';
import FormFieldValue from './FormFieldValue';

import {
  getBusinessObject
} from 'bpmn-js/lib/util/ModelUtil';

import {
  createElement
} from '../../../utils/ElementUtil';

import {
  useService
} from '../../../hooks';


const CUSTOM_TYPE_VALUE = '',
      DEFINED_TYPE_VALUES = [ 'boolean', 'date', 'enum', 'long', 'string', undefined ];

export default function FormField(props) {
  const {
    idPrefix,
    element,
    formField
  } = props;

  const entries = [
    {
      id: idPrefix + '-formFieldID',
      component: <Id idPrefix={ idPrefix } element={ element } formField={ formField } />
    },
    {
      id: idPrefix + '-formFieldLabel',
      component: <Label idPrefix={ idPrefix } element={ element } formField={ formField } />
    },
    {
      id: idPrefix + '-formFieldType',
      component: <Type idPrefix={ idPrefix } element={ element } formField={ formField } />
    },
    DEFINED_TYPE_VALUES.includes(formField.get('type')) ? {} :
      {
        id: idPrefix + '-formFieldCustomType',
        component: <CustomType idPrefix={ idPrefix } element={ element } formField={ formField } />
      },
    {
      id: idPrefix + '-formFieldDefaultValue',
      component: <DefaultValue idPrefix={ idPrefix } element={ element } formField={ formField } />
    },
    formField.get('type') !== 'enum' ? {} :
      {
        id: idPrefix + '-formFieldValues',
        component: <ValueList id={ idPrefix + '-values' } element={ element } formField={ formField } />
      },
    {
      id: idPrefix + '-formFieldConstraints',
      component: <ConstraintList id={ idPrefix + '-constraints' } element={ element } formField={ formField } />
    },
    {
      id: idPrefix + '-formFieldProperties',
      component: <PropertiesList id={ idPrefix + '-properties' } element={ element } formField={ formField } />
    }
  ];

  return entries;
}

function Id(props) {
  const {
    idPrefix,
    element,
    formField
  } = props;

  const commandStack = useService('commandStack');
  const translate = useService('translate');
  const debounce = useService('debounceInput');

  const setValue = (value) => {
    commandStack.execute('properties-panel.update-businessobject', {
      element,
      businessObject: formField,
      properties: {
        id: value
      }
    });
  };

  const getValue = () => {
    return formField.get('id');
  };

  return TextField({
    element: formField,
    id: idPrefix + '-formFieldID',
    label: translate('ID'),
    description: translate('Refers to the process variable name'),
    getValue,
    setValue,
    debounce
  });
}

function Label(props) {
  const {
    idPrefix,
    element,
    formField
  } = props;

  const commandStack = useService('commandStack');
  const translate = useService('translate');
  const debounce = useService('debounceInput');

  const setValue = (value) => {
    commandStack.execute('properties-panel.update-businessobject', {
      element,
      businessObject: formField,
      properties: {
        label: value
      }
    });
  };

  const getValue = () => {
    return formField.get('label');
  };

  return TextField({
    element: formField,
    id: idPrefix + '-formFieldLabel',
    label: translate('Label'),
    getValue,
    setValue,
    debounce
  });
}

function Type(props) {
  const {
    idPrefix,
    element,
    formField
  } = props;

  const commandStack = useService('commandStack');
  const translate = useService('translate');

  const setValue = (value) => {
    commandStack.execute('properties-panel.update-businessobject', {
      element,
      businessObject: formField,
      properties: {
        type: value
      }
    });
  };

  const getValue = () => {
    const type = formField.get('type');

    return DEFINED_TYPE_VALUES.includes(type) ?
      type :
      CUSTOM_TYPE_VALUE;
  };

  const getOptions = () => {
    const options = [
      { label: translate('boolean'), value: 'boolean' },
      { label: translate('date'), value: 'date' },
      { label: translate('enum'), value: 'enum' },
      { label: translate('long'), value: 'long' },
      { label: translate('string'), value: 'string' },
      { label: translate('<custom type>'), value: CUSTOM_TYPE_VALUE }
    ];

    // for the initial state only, we want to show an empty state
    if (formField.get('type') === undefined) {
      options.unshift({ label: translate('<none>'), value: '' });
    }

    return options;
  };

  return SelectEntry({
    element: formField,
    id: idPrefix + '-formFieldType',
    label: translate('Type'),
    getValue,
    setValue,
    getOptions
  });
}

function CustomType(props) {
  const {
    idPrefix,
    element,
    formField
  } = props;

  const commandStack = useService('commandStack');
  const translate = useService('translate');
  const debounce = useService('debounceInput');

  const setValue = (value) => {
    const type = value || '';

    commandStack.execute('properties-panel.update-businessobject', {
      element,
      businessObject: formField,
      properties: {
        type
      }
    });
  };

  const getValue = () => {
    return formField.get('type');
  };

  return TextField({
    element: formField,
    id: idPrefix + '-formFieldCustomType',
    label: translate('Custom type'),
    getValue,
    setValue,
    debounce
  });
}

function DefaultValue(props) {
  const {
    idPrefix,
    element,
    formField
  } = props;

  const commandStack = useService('commandStack');
  const translate = useService('translate');
  const debounce = useService('debounceInput');

  const setValue = (value) => {
    commandStack.execute('properties-panel.update-businessobject', {
      element,
      businessObject: formField,
      properties: {
        defaultValue: value
      }
    });
  };

  const getValue = () => {
    return formField.get('defaultValue');
  };

  return TextField({
    element: formField,
    id: idPrefix + '-formFieldDefaultValue',
    label: translate('Default value'),
    getValue,
    setValue,
    debounce
  });
}

function ValueList(props) {
  const {
    id,
    element,
    formField
  } = props;

  const bpmnFactory = useService('bpmnFactory');
  const commandStack = useService('commandStack');
  const translate = useService('translate');

  const values = formField.get('values') || [];

  function Value(value, index, open) {
    return (
      <CollapsibleEntry
        id={ `${id}-value-${index}` }
        entries={ FormFieldValue({ idPrefix: `${id}-field-${index}`, element, value }) }
        label={ value.get('id') || translate('<empty>') }
        open={ open }
      />
    );
  }

  function addValue() {
    const value = createElement(
      'camunda:Value',
      { id: undefined, name: undefined },
      formField,
      bpmnFactory
    );

    commandStack.execute('properties-panel.update-businessobject-list', {
      element: element,
      currentObject: formField,
      objectsToAdd: [ value ],
      propertyName: 'values'
    });
  }

  function removeValue(value) {
    commandStack.execute('properties-panel.update-businessobject-list', {
      element: element,
      currentObject: formField,
      objectsToRemove: [ value ],
      propertyName: 'values'
    });
  }

  return <ListEntry
    element={ element }
    autoFocusEntry={ true }
    id={ id }
    label={ translate('Values') }
    items={ values }
    renderItem={ Value }
    onAdd={ addValue }
    onRemove={ removeValue }
  />;
}

function ConstraintList(props) {
  const {
    id,
    element,
    formField
  } = props;

  const bpmnFactory = useService('bpmnFactory');
  const commandStack = useService('commandStack');
  const translate = useService('translate');

  const businessObject = getBusinessObject(element);

  let validation = formField.get('validation');

  const constraints = (validation && validation.get('constraints')) || [];

  function Constraint(constraint, index, open) {
    return (
      <CollapsibleEntry
        id={ `${id}-constraint-${index}` }
        entries={ FormFieldConstraint({ idPrefix: `${id}-field-${index}`, element, constraint }) }
        label={ constraint.get('name') || translate('<empty>') }
        open={ open }
      />
    );
  }

  function addConstraint() {
    const commands = [];

    // (1) ensure validation
    if (!validation) {
      validation = createElement(
        'camunda:Validation',
        { },
        businessObject,
        bpmnFactory
      );

      commands.push({
        cmd: 'properties-panel.update-businessobject',
        context: {
          element: element,
          businessObject: formField,
          properties: { validation }
        }
      });
    }

    // (2) add constraint
    const constraint = createElement(
      'camunda:Constraint',
      { name: undefined, config: undefined },
      validation,
      bpmnFactory
    );

    commands.push({
      cmd: 'properties-panel.update-businessobject-list',
      context: {
        element: element,
        currentObject: validation,
        objectsToAdd: [ constraint ],
        propertyName: 'constraints'
      }
    });

    // (3) commit updates
    commandStack.execute('properties-panel.multi-command-executor', commands);
  }

  function removeConstraint(constraint) {
    commandStack.execute('properties-panel.update-businessobject-list', {
      element: element,
      currentObject: validation,
      objectsToRemove: [ constraint ],
      propertyName: 'constraints'
    });
  }

  return <ListEntry
    element={ element }
    autoFocusEntry={ `[data-entry-id="${id}-constraint-${constraints.length - 1}"] input` }
    id={ id }
    label={ translate('Constraints') }
    items={ constraints }
    renderItem={ Constraint }
    onAdd={ addConstraint }
    onRemove={ removeConstraint } />;
}

function PropertiesList(props) {
  const {
    id,
    element,
    formField
  } = props;

  const bpmnFactory = useService('bpmnFactory');
  const commandStack = useService('commandStack');
  const translate = useService('translate');

  const businessObject = getBusinessObject(element);

  let properties = formField.get('properties');

  const propertyEntries = (properties && properties.get('values')) || [];

  function Property(property, index, open) {
    return (
      <CollapsibleEntry
        id={ `${id}-constraint-${index}` }
        entries={ FormFieldProperty({ idPrefix: `${id}-field-${index}`, element, property }) }
        label={ property.get('id') || translate('<empty>') }
        open={ open }
      />
    );
  }

  function addProperty() {
    const commands = [];

    // (1) ensure properties
    if (!properties) {
      properties = createElement(
        'camunda:Properties',
        { },
        businessObject,
        bpmnFactory
      );

      commands.push({
        cmd: 'properties-panel.update-businessobject',
        context: {
          element: element,
          businessObject: formField,
          properties: { properties }
        }
      });
    }

    // (2) add property
    const property = createElement(
      'camunda:Property',
      { id: undefined, value: undefined },
      properties,
      bpmnFactory
    );

    commands.push({
      cmd: 'properties-panel.update-businessobject-list',
      context: {
        element: element,
        currentObject: properties,
        objectsToAdd: [ property ],
        propertyName: 'values'
      }
    });

    // (3) commit updates
    commandStack.execute('properties-panel.multi-command-executor', commands);
  }

  function removeProperty(property) {
    commandStack.execute('properties-panel.update-businessobject-list', {
      element: element,
      currentObject: properties,
      objectsToRemove: [ property ],
      propertyName: 'values'
    });
  }

  return <ListEntry
    element={ element }
    autoFocusEntry={ true }
    id={ id }
    compareFn={ createAlphanumericCompare('id') }
    label={ translate('Properties') }
    items={ propertyEntries }
    renderItem={ Property }
    onAdd={ addProperty }
    onRemove={ removeProperty }
  />;
}

// helper //////////////////

function createAlphanumericCompare(field) {
  return function(entry, anotherEntry) {
    const [ key = '', anotherKey = '' ] = [ entry[field], anotherEntry[field] ];

    return key === anotherKey ? 0 : key > anotherKey ? 1 : -1;
  };
}
